Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Policy Expressions, takes 2 and 3 #144

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

nicowilliams
Copy link
Contributor

@nicowilliams nicowilliams commented Sep 15, 2021

             WIP WIP WIP -- minimally tested

Let's try a different approach to expressing complex policies:

  • Policies as restricted bash scripts...

  • ...that can do very little besides evaluate TPM policies, and which policy scripts are invoked via...

  • ...a driver script, sbin/tpm2-policy, that sets up the environment for running the policy, and takes optional additional artifacts to make available to the policy, then runs it via /bin/rbash.

  • Policy script would run in the following restricted environment:

    • a temp dir as the current directory, with $TMPDIR set to $PWD and in which all the necessary artifacts, including the policy script itself, shall have been placed

    • various TPM2_POLICY... env vars, mainly TPM2_POLICY_SESSION (naming a saved TPM authorization context file)

    • $PATH set to have just one element: an rbin (see below).

  • Artifacts can be supplied by the caller of sbin/tpm2-policy, or they can be embedded in the policy script as here documents, which can be extracted into the current directory (without clobbering any files) via the rbin/writeartifact program.

    All artifacts end up being files in ..

  • There are two types of artifacts:

    • "constant artifacts" that are either embedded in the script or provided along with the script and named via the -F/--file argument to sbin/tpm2-policy:

      • signer keys to be loadexternaled for use with policysigned and policyauthorize
      • signer key names for policyticket and such
      • duplicated keys for importing and loading
      • policyRef values
      • etc.
    • dynamic artifacts that are constructed while the policy is running, such as:

      • saved object context files for signer keys for policysigned and
        policyauthorize

      • tickets from verifysignature, policysecret, and policysigned

      • timeout files

      • etc.

  • The "rbin" directory in the PATH for the execution of policy scripts, with:

    • wrappers for all the tpm2_policy... commands
      • the wrapper for tpm2_policyor is a bit special, naturally
    • wrappers for tpm2, tpm2_import, tpm2_loadexternal and tpm2_verifysignature, and possibly others
    • writeartifact -- a wrapper around xxd and cat for creating files from stdin so that artifacts can be embedded as here documents in the script

This way policies get all the expressive power of bash, and access to all the functionality of tpm2-tools' policy commands.

TBD:

  • Test, debug, test, ...

  • Maybe allow execution of sbin/tpm2-recv by wrapping it in the rbin?

  • Add sample policies that are interesting, like using a combination of policysigned and policyauthorize and curl (outside the policy script) to execute an external script that varies at runtime, or policies that use different passwords for N different users, or which use policysigned to let some other entity authenticate N different users (using policyRef to name them, say), policies requiring golden PCRs OR external policy, etc. Showcase policyor!

    It'd be very nice to be able to have a policy like this:

          (golden PCRs && user authValue)
       || (admin authValue OTP && NV revocation of OTP index)
       || (superadmin authValue)
       || ($policy_containing_policy_signed && policyauthorize_of_it)

Perhaps with multiple superadmin authValues via policyor.

One possible and interesting policy would be to use policyauthorize to allow a policy to be pluggable, but to have the signer only sign policy plugins that contain policysigned w/ nonceTpm. This is interesting because TPM2_PolicyAuthorize() does not provide any way to expire or revoke signatures, but TPM2_PolicySigned() does have a way to strongly bind a signature to a particular session (via nonceTpm inclusion in the hash extended into the policyDigest), and also a way to expire the signature (via time relative to the start of the session).

Use of keys TPM2_Duplicate()ed to the TPM to deliver authValues as passwords for different users also seems interesting.

Such a policy would allow a laptop to boot normally with a user password, and after emergency updates boot with an admin OTP, or after any mishaps via a superadmin password, or with a separate policy that blesses the current PCRs as golden.

Such a policy could be used for sealing an NV index, or it could be set on local storage keys encrypted to the TPM's EKpub via sbin/tpm2-send, allowing for unattended server booting post-attestation, or attended server booting post-mishap (if the encrypted assets get stored "in the clear").

@osresearch osresearch self-requested a review September 15, 2021 17:30
@osresearch osresearch marked this pull request as draft September 15, 2021 17:31
@nicowilliams nicowilliams force-pushed the experiment2 branch 14 times, most recently from 5091162 to 6f38748 Compare September 16, 2021 04:28
@nicowilliams
Copy link
Contributor Author

Ok! I've a trivial test script to demo sbin/tpm2-policy.

@nicowilliams
Copy link
Contributor Author

I've also added long options for bash scripts. I should probably separate getopts_long() into its own commit though.

Also, I should make a policyor() shell function so that alternations can be handled without having to run the policyor restricted bash scripting wrapper.

@nicowilliams
Copy link
Contributor Author

nicowilliams commented Sep 22, 2021

So, I have explored enough that I can write a jq+bash+tpm2-tools thing now. A policy consists of: a) parameters, b) some (possibly none) bindings for those parameters, possibly referenced by URI, or possibly to be prompted for, c) the actual policy commands. Because one might want to create a policy as just a reference to another but with some parameters bound, a policy should itself be possible to refer to either by value or by URI, and, really, any subset of the policy should be possible to express either directly or by reference.

So we're looking at something like:

{
  "name":"...",
  "params":[...],
  "bindings":[...],
  "policy":"<URI>"
}

or

{
  "name":"...",
  "params":[...],
  "bindings":[...],
  "policy":[...]
}

Each policy command would be either a string containing a URI or an object naming the policy command and its command parameters, and each command parameter could be inlined or a reference to a parameter to the policy, or -in the case of PolicyOr- a sub-policy. URIs could be relative, in which case they might be defined elsewhere in the same JSON text or be a reference to another policy provided by the caller or lying around alongside the referrer.

Each parameter would be an object with a name and a type, and possibly some default content.

Each binding would be an object with a name of a parameter and contents for it.

Parameter types would include:

  • PEM public and/or private keys for loading with TPM2_LoadExternal()
  • TPM2B_PUBLIC values, base64-encoded, for loading with TPM2_LoadExternal()
  • outputs of TPM2_Duplicate(), base64-encoded, for loading with TPM2_Import() and TPM2_load()
  • signatures as JSON objects with hash and signature algorithms, signature format, and base64-encoded signature value, for verification with TPM2_VerifySignature()
  • names of tickets for use with tickets created by TPM2_VerifySignature(), TPM2_PolicySigned() or TPM2_PolicySecret()`
  • prompts for passwords and how to process them into authValues

Perhaps a parameter could specify some default content but not all (e.g., hash and signature algorithms, but not signatures, naturally), so that a binding need only provide the missing parts.

References to parameters would become appropriate values of the appropriate types in the actual policy commands. E.g., object contexts might get loaded and referred to by handle, tickets might be passed in by value, etc.

PolicySigned and PolicySecret commands could be replaced automatically with PolicyTicket as needed.

Perhaps a policy could refer to or include another and specify alternatives for PolicyOr commands in the other. For example, one might have a policy that allows for N users to authenticate with a smartcard or password, say, and one might then define a policy that refers to that policy and specifies the PolicyOr alternatives identifying one particular user.

There would be two evaluation modes: trial (to compute a policyDigest for a policy), and authorization (to satisfy a policy and get access to a protected resource). For trial sessions fewer parameters may need bindings at evaluation time than for authorization sessions -- the evaluator needs to understand this specifically, and more generally needs to know which parameters need bindings and which don't. Indeed, an evaluator might choose alternatives to evaluate based on the parameters for which there are bindings, that or it must be told explicitly. The evaluator should then import and/or load all the TPM objects specified, save their contexts as a needed, flush TPM objects as needed, verify signatures, and load saved objects as needed right before executing policy commands that refer to them.

@nicowilliams
Copy link
Contributor Author

Parameters have to go with the policy definitions. Default bindings can too. External bindings would be provided by other policies or command-line arguments.

Internal parameters would be a thing (tickets, for example, things output by TPM commands internal to the policy that are needed by other commands).

So maybe something more like:

{
  "name":"...",
  "bindings":[...],
  "policy": "<URI>"
}

or

{
  "name":"...",
  "bindings":[...],
  "policy": {
    "parameters":[...],
    "policyDef":[...]
}

A policyDef would be an array of:

{
  "command":"policyX",
  "cp<Name>":"<value> or else an object with a param reference",
  "...":"...",
  "rp<Name>":{"name":"<internal-param-name>","display":true},
  "...":...
}

A parameter reference would be:

{  "name":"<parameter-name>", "sub":"<sub-name>"  }

where <sub-name> might be something like "name", "hash-alg", "sign-alg", etc., for referring to an aspect of a parameter.

Policy holes (PolicyAuthorize, PolicyAuthorizeNV, and PolicyOr) have to be first. The alternatives to PolicyOr would be policies, either defined inline or referenced by URI.

A parameter needs:

  • a name
  • an indication of whether it's internal (maybe we can avoid needing to declare internal parameters)
  • a type (e.g., "PEM public key", "PEM private key", "TPM2B_PUBLIC", etc.)
  • an optional default value (may be overridden) or an optional hard-coded value (can't be overridden)

We might want to make things like hash and signature algorithms ancillary properties of a parameter naming a key, or we might want to make them separate parameters. I think the former.

{
  "name":"...",
  "internal":false,
  "type":"TPM2B_PUBLIC",
  "default":"<base64-encoded>"
}

A binding might be a prompt for a value or an actual value. If the parameter is something like a cryptographic key, then the value might be multipart (one part might be the key, another might be the various associated algorithms).

@nicowilliams
Copy link
Contributor Author

I'm also thinking that this rbash prototype may be good enough for now because it's... simple and easy.

@nicowilliams
Copy link
Contributor Author

I've pushed a WIP of take 3, this time using JSON and jq, all inspired by take 2.

@nicowilliams nicowilliams changed the title Policy Expressions, take 2 Policy Expressions, takes 2 and 3 Sep 27, 2021
@nicowilliams
Copy link
Contributor Author

https://trustedcomputinggroup.org/wp-content/uploads/TSS_JSON_Policy_v0p7_r08_pub.pdf

The code in take 3 is not implementing that.

We should be able to consume PEM and not have to convert it to
TPM2B_PUBLIC.  However, the tpm2-tools {tpm2 makecredential} and
{tpm2 duplicate} commands can't handle that, and we use those in
sbin/tpm2-send.

For now we start an SW TPM per-invocation, and tear it down when
exiting.
@nicowilliams nicowilliams force-pushed the experiment2 branch 2 times, most recently from 29ec869 to dd6d12f Compare October 1, 2021 21:31
                WIP WIP WIP -- not tested

Let's try a different approach to expressing complex policies:

 - Policies as restricted bash scripts that can do very little besides
   evaluate TPM policies, and which are invoked via...

 - ...a driver script, `sbin/tpm2-policy`, that sets up the environment
   for running the policy, and takes optional additional artifacts to
   make available to the policy.

 - Constant artifacts would be things like:

    - signer keys for `policysigned` and `policyauthorize`
    - signer key names for `policyticket` and such
    - anything else you can imagine

   Non-constant artifacts would be things like:

    - saved object context files for signer keys for `policysigned` and
      `policyauthorize`

    - tickets from `verifysignature`, `policysecret`, and `policysigned`

    - timeout files

    - anything else you can imagine

   all of which can be written only in `.` / `$PWD`.

 - The policy scripts would run in the following environment:

    - a temp dir as the current directory, with `$TMPDIR` set to `$PWD`
      and in which all the necessary artifacts, including the policy script
      itself, shall have been placed

    - various TPM2_POLICY... env vars, mainly TPM2_POLICY_SESSION

    - `$PATH` set to have just two paths: an `rbin` (see below), and the
      `$PWD`/`$TMPDIR` itself into which the policy script will have
      been copied (this will allow the policy script to implement
      different sub-policies selected by its arguments by executing itself,
      possibly through the `rbin/policyor` wrapper).

 - The "rbin" directory in the PATH for the execution of policy scripts,
   with:

    - wrappers for all the tpm2_policy... commands
    - the wrapper for tpm2_policyor is a bit special, naturally
    - wrappers for tpm2_loadexternal and tpm2_verifysignature, and
      possibly others
    - links to or wrappers of a handful of useful system commands like
      `sha256sum`, `xxd`, `dc`, `bc`, `jq`, etc.
    - a wrapper around `xxd` and `cat` for creating files from stdin so that
      artifacts can be embedded as here documents in the script

This way policies get all the expressive power of bash, and access to all the
functionality of tpm2-tools' policy commands.

TBD:

 - Test, debug, test, ...

 - Add rbin wrappers for importing duplicated keys (so they can provide
   authValues discretely!).

 - Maybe allow execution of `sbin/tpm2-recv` by linking it from the rbin?

 - Add sample policies that are interesting, like using a combination of
   `policysigned` and `policyauthorize` and `curl` (outside the policy
   script) to execute an external script that varies at runtime, or
   policies that use different passwords for N different users, or which
   use `policysigned` to let some other entity authenticate N different
   users (using `policyRef` to name them, say), policies requiring
   golden PCRs OR external policy, etc.  Showcase `policyor`!

   It'd be very nice to be able to have a policy like this:

         (golden PCRs && user authValue)
      || (admin authValue OTP && NV revocation of OTP index)
      || (superadmin authValue)
      || ($policy_containing_policy_signed && policyauthorize_of_it)

   Perhaps with multiple superadmin authValues via `policyor`.

   Such a policy would allow a laptop to boot normally with a user
   password, and after emergency updates boot with an admin OTP, or
   after any mishaps via a superadmin password, or with a separate
   policy that blesses the current PCRs as golden.

   Such a policy could be used for sealing an NV index, or it could be
   set on local storage keys encrypted to the TPM's EKpub via
   `sbin/tpm2-send`, allowing for unattended server booting
   post-attestation, or attended server booting post-mishap (if the
   encrypted assets get stored "in the clear").
This is a sketch of how to represent and implement policies expressed in
JSON using "take 2" as a model.

Policies are represented as JSON objects that have:

 - zero, one, or more named policy parameters, which have type
   information

 - zero, one, or more bindings for a policy's parameters -- these can be
   default values, or values for parameters of other policies referred
   to by this one

 - an actual policy AST

A policy can refer to other policies.  This is especially necessary for
TPM2_PolicyAuthorize() and TPM2_PolicyAuthorizeNV(), where the
referred-to policy may not be known until run-time, so we really have to
be able to separate the referrent and the referred-to policies.  This
may also be useful for TPM2_PolicyOr() even though its alternatives are
static -- it may help organize policies, and to DRY.

We treat TPM2_PolicyOr() as AST interior nodes.  Interior nodes have to
be singular TPM2_PolicyOr() commands.  Leaf nodes are sequences of
commands the first of which is allowed to be a hole, like
TPM2_PolicyAuthorize() or TPM2_PolicyAuthorizeNV().

See ./policy.jq!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant